#region Header

// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
//     <version>$Revision: 1965 $</version>
// </file>

#endregion Header

namespace ICSharpCode.TextEditor
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;

    using ICSharpCode.TextEditor.Document;

    /// <summary>
    /// A class that is able to draw a line on any control (outside the text editor)
    /// </summary>
    public class DrawableLine
    {
        #region Fields

        static StringFormat sf = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone();

        Font boldMonospacedFont;
        Font monospacedFont;
        SizeF spaceSize;
        List<SimpleTextWord> words = new List<SimpleTextWord>();

        #endregion Fields

        #region Constructors

        public DrawableLine(IDocument document, LineSegment line, Font monospacedFont, Font boldMonospacedFont)
        {
            this.monospacedFont = monospacedFont;
            this.boldMonospacedFont = boldMonospacedFont;
            if (line.Words != null) {
                foreach (TextWord word in line.Words) {
                    if (word.Type == TextWordType.Space) {
                        words.Add(SimpleTextWord.Space);
                    } else if (word.Type == TextWordType.Tab) {
                        words.Add(SimpleTextWord.Tab);
                    } else {
                        words.Add(new SimpleTextWord(TextWordType.Word, word.Word, word.Bold, word.Color));
                    }
                }
            } else {
                words.Add(new SimpleTextWord(TextWordType.Word, document.GetText(line), false, Color.Black));
            }
        }

        #endregion Constructors

        #region Properties

        public int LineLength
        {
            get {
                int length = 0;
                foreach (SimpleTextWord word in words) {
                    length += word.Word.Length;
                }
                return length;
            }
        }

        #endregion Properties

        #region Methods

        public static float DrawDocumentWord(Graphics g, string word, PointF position, Font font, Color foreColor)
        {
            if (word == null || word.Length == 0) {
                return 0f;
            }
            SizeF wordSize = g.MeasureString(word, font, 32768, sf);

            g.DrawString(word,
                         font,
                         BrushRegistry.GetBrush(foreColor),
                         position,
                         sf);
            return wordSize.Width;
        }

        public void DrawLine(Graphics g, ref float xPos, float xOffset, float yPos, Color c)
        {
            SizeF spaceSize = GetSpaceSize(g);
            foreach (SimpleTextWord word in words) {
                switch (word.Type) {
                    case TextWordType.Space:
                        xPos += spaceSize.Width;
                        break;
                    case TextWordType.Tab:
                        float tabWidth = spaceSize.Width * 4;
                        xPos += tabWidth;
                        xPos = (int)((xPos + 2) / tabWidth) * tabWidth;
                        break;
                    case TextWordType.Word:
                        xPos += DrawDocumentWord(g,
                                                 word.Word,
                                                 new PointF(xPos + xOffset, yPos),
                                                 word.Bold ? boldMonospacedFont : monospacedFont,
                                                 c == Color.Empty ? word.Color : c
                                                );
                        break;
                }
            }
        }

        public void DrawLine(Graphics g, ref float xPos, float xOffset, float yPos)
        {
            DrawLine(g, ref xPos, xOffset, yPos, Color.Empty);
        }

        public SizeF GetSpaceSize(Graphics g)
        {
            if (spaceSize.IsEmpty) {
                spaceSize = g.MeasureString("-", boldMonospacedFont,  new PointF(0, 0), sf);
            }
            return spaceSize;
        }

        public float MeasureWidth(Graphics g, float xPos)
        {
            SizeF spaceSize = GetSpaceSize(g);
            foreach (SimpleTextWord word in words) {
                switch (word.Type) {
                    case TextWordType.Space:
                        xPos += spaceSize.Width;
                        break;
                    case TextWordType.Tab:
                        float tabWidth = spaceSize.Width * 4;
                        xPos += tabWidth;
                        xPos = (int)((xPos + 2) / tabWidth) * tabWidth;
                        break;
                    case TextWordType.Word:
                        if (word.Word != null && word.Word.Length > 0) {
                            xPos += g.MeasureString(word.Word, word.Bold ? boldMonospacedFont : monospacedFont, 32768, sf).Width;
                        }
                        break;
                }
            }
            return xPos;
        }

        public void SetBold(int startIndex, int endIndex, bool bold)
        {
            if (startIndex < 0)
                throw new ArgumentException("startIndex must be >= 0");
            if (startIndex > endIndex)
                throw new ArgumentException("startIndex must be <= endIndex");
            if (startIndex == endIndex) return;
            int pos = 0;
            for (int i = 0; i < words.Count; i++) {
                SimpleTextWord word = words[i];
                if (pos >= endIndex)
                    break;
                int wordEnd = pos + word.Word.Length;
                // 3 possibilities:
                if (startIndex <= pos && endIndex >= wordEnd) {
                    // word is fully in region:
                    word.Bold = bold;
                } else if (startIndex <= pos) {
                    // beginning of word is in region
                    int inRegionLength = endIndex - pos;
                    SimpleTextWord newWord = new SimpleTextWord(word.Type, word.Word.Substring(inRegionLength), word.Bold, word.Color);
                    words.Insert(i + 1, newWord);

                    word.Bold = bold;
                    word.Word = word.Word.Substring(0, inRegionLength);
                } else if (startIndex < wordEnd) {
                    // end of word is in region (or middle of word is in region)
                    int notInRegionLength = startIndex - pos;

                    SimpleTextWord newWord = new SimpleTextWord(word.Type, word.Word.Substring(notInRegionLength), word.Bold, word.Color);
                    // newWord.Bold will be set in the next iteration
                    words.Insert(i + 1, newWord);

                    word.Word = word.Word.Substring(0, notInRegionLength);
                }
                pos = wordEnd;
            }
        }

        #endregion Methods

        #region Nested Types

        private class SimpleTextWord
        {
            #region Fields

            internal static readonly SimpleTextWord Space = new SimpleTextWord(TextWordType.Space, " ", false, Color.Black);
            internal static readonly SimpleTextWord Tab = new SimpleTextWord(TextWordType.Tab, "\t", false, Color.Black);

            internal bool Bold;
            internal Color Color;
            internal TextWordType Type;
            internal string Word;

            #endregion Fields

            #region Constructors

            public SimpleTextWord(TextWordType Type, string Word, bool Bold, Color Color)
            {
                this.Type = Type;
                this.Word = Word;
                this.Bold = Bold;
                this.Color = Color;
            }

            #endregion Constructors
        }

        #endregion Nested Types
    }
}